package com.servoy.r3.plugins;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.CDATASection;
import org.xml.sax.InputSource;

import java.util.*;

import com.servoy.j2db.util.Debug;

public class JRXMLDocumentProxy {

	public final static String JRXML_QUERYSTRING = "queryString";
	public final static String JRXML_QUERYSTRING_LANGUAGE_ATTR = "language";
	public final static String JRXML_QUERYSTRING_LANGUAGE_ATTR_XPATH_VALUE = "xPath";
	public final static String JRXML_FIELD = "field";
	public final static String JRXML_FIELD_NAME_ATTR = "name";
	public final static String JRXML_FIELD_FIELDDESCRIPTION_ATTR = "fieldDescription";
	public final static String JRXML_SUBREPORT = "subreport";
	public final static String JRXML_PARAMETER_KEY_START = "$P{";
	public final static String JRXML_PARAMETER_KEY_END = "}";
	public final static String JRXML_PARAMETER_TAG = "parameter";
	public final static String JRXML_PARAMETER_NAME_ATTR = "name";
	public final static String JRXML_PARAMETER_CLASS_ATTR = "class";
	public final static String JRXML_PARAMETER_DEFAULTVALUEEXPRESSION_TAG = "defaultValueExpression";
	public final static String JRXML_JASPERREPORT_TAG = "jasperReport";
	public final static String JRXML_RESOURCEBUNDLE_ATTR = "resourceBundle";

	public final static String CLASS_STRING = "java.lang.String";
	public final static String CLASS_INTEGER = "java.lang.Integer";

	private String fileName;
	private Element root;
	private Document xmldoc;
	//private IClientPluginAccess application;

	private HashMap parameters = new HashMap();

	public JRXMLDocumentProxy(/*IClientPluginAccess application,*/ String filename,
			HashMap map) throws Exception{
		this.fileName = filename;
		//this.application = application;
		this.parameters = map;

		try {
			DocumentBuilderFactory dbfactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder dbuilderjr = dbfactory.newDocumentBuilder();

			File orf = new File(fileName);
			xmldoc = dbuilderjr.parse(orf);

			root = xmldoc.getDocumentElement();
		} catch (IOException ioe) {
			Debug.error(ioe);
			throw ioe;
		} catch (DOMException dex) {
			Debug.error(dex);
			throw dex;
		} catch (Exception ex) {
			Debug.error(ex);
			throw ex;
		}
	}

	public JRXMLDocumentProxy(StringReader xmlcontent,
			/*IClientPluginAccess application,*/ String filename,
			HashMap map) throws Exception {

		this.fileName = filename;
		//this.application = application;
		this.parameters = map;

		try {
			DocumentBuilderFactory dbfactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder dbuilderjr = dbfactory.newDocumentBuilder();
			xmldoc = dbuilderjr.parse(new InputSource(xmlcontent));

			root = xmldoc.getDocumentElement();
		} catch (IOException ioe) {
			Debug.error(ioe);
			throw ioe;
		} catch (DOMException dex) {
			Debug.error(dex);
			throw dex;
		} catch (Exception ex) {
			Debug.error(ex);
			throw ex;
		}
	}

	private String getParameterTypeName(String paramname) {
		String partype = "";

		Element param = Utils.getChildElementForNameAndAttribute(this.root,
				JRXML_PARAMETER_TAG, JRXML_PARAMETER_NAME_ATTR, paramname);

		if (param != null) {
			partype = param.getAttribute(JRXML_PARAMETER_CLASS_ATTR);
		}

		return partype;
	}

	private String getparameterDefalutValue(String paramname) {
		String pardefvalue = "null";

		Element param = Utils.getChildElementForNameAndAttribute(this.root,
				JRXML_PARAMETER_TAG, JRXML_PARAMETER_NAME_ATTR, paramname);

		if (param != null) {
			NodeList nodes = param
					.getElementsByTagName(JRXML_PARAMETER_DEFAULTVALUEEXPRESSION_TAG);

			if (nodes.getLength() > 0) {
				Element defval = (Element) nodes.item(0);

				NodeList qchlds = defval.getChildNodes();
				for (int k = 0; k < qchlds.getLength(); k++) {
					Node n = qchlds.item(k);
					if (n.getNodeType() == Node.CDATA_SECTION_NODE) {
						//pardefvalue = n.getTextContent();
						pardefvalue = n.getNodeValue();

						break;
					}
				}
			}
		}

		return pardefvalue;
	}

	private String getParameterForType(String type, String parameter) {
		String param = "";

		if (type.compareTo(CLASS_STRING) == 0) {
			if (parameter.charAt(0) == '\"'
					&& parameter.charAt(parameter.length() - 1) == '\"')
				param = "'" + parameter.substring(1, parameter.length() - 1)
						+ "'";
			else if (parameter.charAt(0) == '\''
					&& parameter.charAt(parameter.length() - 1) == '\'')
				param = parameter;
			else
				param = "'" + parameter + "'";
		} else {
			param = parameter;
		}

		return param;
	}

	private String getStringWithFilledParameters(String str) {

		String result = new StringBuffer(str).toString();

		int start = 0;
		int pos = 0;

		String parname = "";

		while (pos >= 0) {

			pos = result.indexOf(JRXML_PARAMETER_KEY_START, start);

			if (pos >= 0) {
				int epos = result.indexOf(JRXML_PARAMETER_KEY_END, pos);
				parname = result.substring(pos
						+ JRXML_PARAMETER_KEY_START.length(), epos);

				String parval = (String)this.parameters.get(parname);
				if (parval == null) {
					parval = getparameterDefalutValue(parname);
				}

				//System.out.println(parval);

				String partype = getParameterTypeName(parname);

				String parameter = getParameterForType(partype, parval);

				result = Utils.replaceString(pos, epos, result, parameter);

				start = pos;
			}
		}

		return result;
	}

	public String getResourceBundle() {
		String resourceBundle = "";

		/*
		 * NodeList jrquerynodes = root
		 * .getElementsByTagName(JRXML_JASPERREPORT_TAG); if
		 * (jrquerynodes.getLength() > 0) { Element jrtag = (Element)
		 * jrquerynodes.item(0); resourceBundle =
		 * root.getAttribute(JRXML_RESOURCEBUNDLE_ATTR); }
		 */

		resourceBundle = root.getAttribute(JRXML_RESOURCEBUNDLE_ATTR);

		if (resourceBundle == "")
			resourceBundle = Utils
					.getNameWithoutExtensionFromFileName(this.fileName);

		return resourceBundle;
	}

	public String getQueryString(boolean fillParameters) {
		String sqlQuery = "";

		// Get JR sql query
		NodeList jrquerynodes = root.getElementsByTagName(JRXML_QUERYSTRING);
		if (jrquerynodes.getLength() > 0) {
			Element queryStrNode = (Element) jrquerynodes.item(0);

			NodeList qchlds = queryStrNode.getChildNodes();
			for (int k = 0; k < qchlds.getLength(); k++) {
				Node n = qchlds.item(k);
				if (n.getNodeType() == Node.CDATA_SECTION_NODE) {
					//sqlQuery = n.getTextContent();
					sqlQuery = n.getNodeValue();

					if (fillParameters)
						sqlQuery = getStringWithFilledParameters(sqlQuery);

					break;
				}
			}
		}

		return sqlQuery;
	}

	public String updateQueryStringForXML() {
		
		String content = "";
		
		// Update the query string to match the xml connection
		NodeList nodes = root.getElementsByTagName(JRXML_QUERYSTRING);
		if (nodes.getLength() > 0) {
			Element queryStrNode = (Element) nodes.item(0);

			queryStrNode.setAttribute(JRXML_QUERYSTRING_LANGUAGE_ATTR,
					JRXML_QUERYSTRING_LANGUAGE_ATTR_XPATH_VALUE);

			NodeList childs = queryStrNode.getChildNodes();
			for (int i = 0; i < childs.getLength(); i++) {
				Node n = childs.item(i);
				queryStrNode.removeChild(n);
			}

			String cdtxt = XMLSampleData.XML_ROWXPATH1
					+ Base64
							.encodeObject(
									Utils
											.getNameWithoutExtensionFromFileName(this.fileName),
									Base64.URL_SAFE)
					+ XMLSampleData.XML_ROWXPATH2;

			CDATASection txt = xmldoc.createCDATASection(cdtxt);

			queryStrNode.appendChild(txt);
			
			//content = queryStrNode.getOwnerDocument().getTextContent();
			content = queryStrNode.getOwnerDocument().getNodeValue();
		}
		
		return content;
	}

	public void updateQueryStringForSQL(String sqlQuery) {
		// Revert the query to sql
		NodeList nodes = root.getElementsByTagName(JRXML_QUERYSTRING);
		if (nodes.getLength() > 0) {
			Element queryStrNode = (Element) nodes.item(0);

			queryStrNode.removeAttribute(JRXML_QUERYSTRING_LANGUAGE_ATTR);

			NodeList qchlds = queryStrNode.getChildNodes();
			for (int k = 0; k < qchlds.getLength(); k++) {
				Node n = qchlds.item(k);
				queryStrNode.removeChild(n);
			}

			CDATASection cdsqlquery = xmldoc.createCDATASection(sqlQuery);

			queryStrNode.appendChild(cdsqlquery);
		}
	}

	public void updateSubreportsForXML() throws Exception {
		
		/*for (JRXMLSubreport sub : this.getSubreports()) {
			sub.updateSubreportExpresionForXML();
		}*/
		ArrayList subs = this.getSubreports();
		
		for (int i=0; i<subs.size(); i++)
		{
			((JRXMLSubreport)subs.get(i)).updateSubreportExpresionForXML();
		}
	}

	public void updateSubreportsForSQL(HashMap filenames)
			throws Exception {

		//for (JRXMLSubreport sub : this.getSubreports()) {
		
		ArrayList subs = this.getSubreports();
		
		for (int i=0; i<subs.size(); i++){
			JRXMLSubreport sub = (JRXMLSubreport)subs.get(i);

			String subrn = Utils.getFilenameDexmled(sub
					.getSubreportFilename(false));
			String value = (String)filenames.get(subrn);

			if (value != null)
				sub.updateSubreportExpressionForSQL(value);
		}
	}

	public void updateFieldsForXML() {
		// Update the field tags to match xPath addresses in description.
		NodeList fldList = root.getElementsByTagName(JRXML_FIELD);
		for (int i = 0; i < fldList.getLength(); i++) {
			Element fld = (Element) fldList.item(i);

			String fldname = fld.getAttribute(JRXML_FIELD_NAME_ATTR);

			Element fdesc = xmldoc
					.createElement(JRXML_FIELD_FIELDDESCRIPTION_ATTR);

			CDATASection cddesc = xmldoc.createCDATASection(fldname);
			fdesc.appendChild(cddesc);

			fld.appendChild(fdesc);
		}
	}

	public void updateFieldsforSQL() {
		// Remove fields description from the document
		NodeList fldslist = root.getElementsByTagName(JRXML_FIELD);
		for (int i = 0; i < fldslist.getLength(); i++) {
			Element fld = (Element) fldslist.item(i);

			NodeList childs = fld
					.getElementsByTagName(JRXML_FIELD_FIELDDESCRIPTION_ATTR);
			for (int j = 0; j < childs.getLength(); j++) {
				Node n = childs.item(j);
				fld.removeChild(n);
			}
		}
	}

	private void getSubnodeSubreports(ArrayList list, Node nd)
			throws Exception {

		NodeList subslist = nd.getChildNodes();

		try {
			for (int i = 0; i < subslist.getLength(); i++) {
				Node nnd = subslist.item(i);

				if (nnd.getNodeName() == JRXML_SUBREPORT
						&& nnd.getNodeType() == Node.ELEMENT_NODE) {
					JRXMLSubreport subr = new JRXMLSubreport((Element) nnd);
					list.add(subr);

					// String path = subr.getSubreportExpression(true);
					//
					// JRXMLProvider provider = new JRXMLProvider(
					// this.application, path);
					//					
					// provider.AddSubreports(list);

				} else
					getSubnodeSubreports(list, nnd);

			}
		} catch (Exception ex) {
			Debug.error(ex);
			throw ex;
		}
	}

	public ArrayList getSubreports() throws Exception {

		ArrayList list = new ArrayList();

		getSubnodeSubreports(list, root);

		return list;
	}

	public String serializeData(String destinationfilename) throws Exception {
		String result = null;

		try {
			StringWriter stre = new StringWriter();
			Source domSource = new DOMSource(xmldoc);
			Result streamResult = new StreamResult(stre);
			TransformerFactory tf = TransformerFactory.newInstance();
			tf.setAttribute("indent-number", new Integer(2));
			Transformer serializer = tf.newTransformer();
			serializer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
			serializer.setOutputProperty(OutputKeys.INDENT, "yes");
			serializer.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", "2");

			serializer.transform(domSource, streamResult);

			result = stre.toString();

			// Serialize to file if the parameter is not null
			if (destinationfilename != null) {
				File file = new File(destinationfilename);
				Result fileResult = new StreamResult(file);
				serializer.transform(domSource, fileResult);
			}
		} catch (DOMException dex) {
			Debug.error(dex);
			throw dex;
		} catch (Exception ex) {
			Debug.error(ex);
			throw ex;
		}

		return result;
	}

	public HashMap getSubreportsFilenames() throws Exception{
		HashMap list = new HashMap();

//		for (JRXMLSubreport sub : this.getSubreports()) {
		ArrayList subs = this.getSubreports();
		
		for (int i=0; i<subs.size(); i++){
			JRXMLSubreport sub = (JRXMLSubreport)subs.get(i);
			
			String subrfilen = sub.getSubreportFilename(false);
			String key = Utils.getNameWithoutExtensionFromFileName(subrfilen)
					+ "." + Utils.getExtensionFromFileName(subrfilen);

			list.put(key, subrfilen);
		}

		return list;
	}

	public JRXMLDocumentProxy Duplicate() throws Exception{
		return new JRXMLDocumentProxy(
				new StringReader(this.serializeData(null)), /*this.application,*/
				this.fileName, this.parameters);
	}
}
